Tutorial 1: Building a simple guest book

Posted by Dave Redfern (Writer), Tutorials on 12 Apr 2008 @ 13:51

Getting Started

For this tutorial you will need a clean install of the framework, a web server with PHP, PHP CLI tools, MySQL and be able to access the and run the base and baseAdminSites. If you have not configured these yet, then be sure to do so before going any further with this tutorial. Additionally you will need a PHP editor (e.g. Zend Studio, NetBeans 6.5+, PhpStorm etc).

To keep things simple, the tutorial uses all the default paths. If you change any of the paths, be sure to modify the instructions accordingly.

Any code examples listed will use a JavaScript code highlighting engine.

So now we will build our basic table in a database and use the framework to build a basic interface to it.

Building the basics

With MySQL setup and the basic Scorpio databases installed and configured, we can create a new database:

tutorials

and then create a new table "guestbook". We will use the "tutorials" database in all of these tutorial articles. The SQL code for these two is below:

CREATE DATABASE `tutorials` ;

CREATE TABLE `tutorials`.`guestbook` (
`id` INT( 10 ) NOT NULL AUTO_INCREMENT ,
`name` VARCHAR( 100 ) NOT NULL ,
`emailAddress` VARCHAR( 100 ) NOT NULL ,
`ipAddress` VARCHAR( 20 ) NOT NULL ,
`referrer` VARCHAR( 255 ) NOT NULL ,
`comment` TEXT NOT NULL ,
`createDate` DATETIME NOT NULL ,
`updateDate` DATETIME NOT NULL ,
`updatedFor` ENUM( 'Language', 'Off topic', 'In-appropriate Content', 'Other Reason' ) NOT NULL DEFAULT 'Language',
PRIMARY KEY ( `id` )
) ENGINE = MYISAM 

Be sure to update the main config.xml file in /libraries with an entry under the database section for "tutorials".

Building the Guestbook DAO Object

Next, we need a terminal window (command line, this should work for Windows too). Go to your project location and then into the tools directory. Inside there is a script called "scorpio.php". You can run this by calling:

php scorpio.php

This will run the script and show you the list of options available. Right now, we are going to list the databases:

php scorpio.php list database all

Our "tutorials" database should be listed. Next we can list all the tables within the tutorials database using:

php scorpio.php list database tutorials

Now we are going to generate our guestbook DAO object.

php scorpio.php new dao tutorials.guestbook --classname=guestbook

Hit enter, and it should complete without trouble. You may need to create the target folder first. This example attempts to create a class named "guestbook.class.php" within a sub-folder called guestbook in the main classes folder within the framework root in the current project folder.

You can check that this was built correctly by browsing to the folder and opening up the file. You can see what generator has done - it has built the basic structure for a DAO object including all the properties and methods to save, load and delete records as well as listing multiple records. All the properties are protected and are accessed via get/set methods. This is working from the default template for class files. If this is not to your liking you can create any number of custom templates. This will be covered in a separate tutorial looking at all of the generator features.

You will need to create an autoload file in the classes/autoload folder. This is a very simple file that just returns an array of the classname with the path to the file relative to the classes folder:

return array(
    'guestbook' => 'guestbook/guestbook.class.php',
);

Testing the object

We can additionally create a test case skeleton for our class by using the scorpio CLI tool to build one for us.

To do this we run the following command:

php scorpio.php new test guestbook --package=tutorials

This will create a test case within the /data/tests folder (unless configured differently).

To run the test case you need to return to the command line, and then run:

php testSuite.php test package tutorials guestbook

The test will run and should return mostly skipped entries - it is up to you to complete the test case.

The admin system

Any guestbook needs to have some kind of management system for it. This example uses the baseAdminSite component that is provided separately. You can obtain this from the downloads section on SourceForge.

Now that we have our generated and tested DAO object we can look at building the admin pages for it. This requires a little extra work under normal circumstances, however for the sake of simplicity these additional steps are covered in later tutorials.

Once again, return to the command line and head back to the tools folder. We will now run the following script:

php scorpio.php

Any tool or CLI script (if configured) can be executed without any arguments to list the available options - this is built into the CLI application layer.

Any controller needs to be built for a site. You can get the list of currently configured sites by running:

php scorpio.php list sites

Make a note of the site name you want to add the controller to.

To build the admin tools we then issue the following command:

php scorpio.php new controller controlPanel/websiteAdmin/guestbookAdmin --site=baseAdminSite --description="Guestbook Admin" --dao=guestbook

Hit enter and a new set of folders and files should be created. If the guestbook class cannot be loaded, go back and check that the autoload file was created correctly. The folders have all been created in "baseAdminSite/controllers/controlPanel" with first a "websiteAdmin" folder plus 3 files and then a "guestbookAdmin" folder with another 3 files. The templates for the view layer will be in a mirrored set of folders within the baseAdminSite/views folder.

Configuring the controllers

Now before we can use these components, the various files must be edited. First we need to modify the websiteAdmin view. This will simply list sub-controllers and have no actions itself (we could even delete the model file). To do this, open the websiteAdminView.class.php and change the following code:

class websiteAdminView extends mvcView {

	/**
	 * @see mvcViewBase::__construct()
	 */
	function __construct($inController) {
		parent::__construct($inController) ;
	
	}
	
	/**
	 * Shows the websiteAdminView page
	 *
	 * @return void
	 */
	function showWebsiteAdminPage() {
		$this->setCacheLevelNone();
		
		$this->render($this->getTpl('websiteAdmin'));
	}
}

to:

class websiteAdminView extends mvcView {

	/**
	 * @see mvcViewBase::__construct()
	 */
	function __construct($inController) {
		parent::__construct($inController) ;
	
		$this->getEngine()->assign('selected', 'controlPanel');
		$this->getEngine()->assign('controllerHelpPage', 'websiteAdminController');
		$this->getEngine()->assign('parentController', 'home');
	}
	
	/**
	 * Shows the websiteAdminView page
	 *
	 * @return void
	 */
	function showWebsiteAdminPage() {
		$this->setCacheLevelNone();
		
		$this->render($this->getTpl('controllerListPage', '/shared'));
	}
}

Next, open the guestbookAdminController and Model files. We need to update the filters and make sure that we set the primary key correctly in both the controller and the model. Find the following lines in the controller:

/**
 * @see mvcControllerBase::addInputToModel()
 */
function addInputToModel($inData, $inModel) {
	/**
	 * @todo set the primary key here
	 */
	//$inModel->setPrimaryKey($inData['PrimaryKey']);
	$inModel->setId($inData['Id']);
	$inModel->setName($inData['Name']);
	$inModel->setEmailAddress($inData['EmailAddress']);
	$inModel->setIpAddress($inData['IpAddress']);
	$inModel->setReferrer($inData['Referrer']);
	$inModel->setComment($inData['Comment']);
	$inModel->setUpdatedFor($inData['UpdatedFor']);
}

and change it to:

/**
 * @see mvcControllerBase::addInputToModel()
 */
function addInputToModel($inData, $inModel) {
	$inModel->setId($inData['PrimaryKey']);
	$inModel->setName($inData['Name']);
	$inModel->setEmailAddress($inData['EmailAddress']);
	$inModel->setIpAddress($inData['IpAddress']);
	$inModel->setReferrer($inData['Referrer']);
	$inModel->setComment($inData['Comment']);
	$inModel->setUpdatedFor($inData['UpdatedFor']);
}

Now, in the model update the database and table names in the method getTotalObjects() and finally, again in the model, set the primary key for loading in the method getExistingObject() to use the correct method (setId()).

Save all the changes and switch to your web-browser.

The admin system continued...

Login to the admin system using the default root account (unless you created another account already - you will need ROOT access though).

Navigate to Control Panel (notice that Website Admin does NOT appear) and Site Editor. Go to Controllers. Once the Controllers applet opens, check that there is an entry for guestbookAdmin in the list of available controllers for the default plugin. If it is NOT there, manually add it using the "Add" button.

Next, using the menu on the right of the screen (below Control Panel), select Controller Map. Once this applet loads, edit site 2. You will see a list of all the available controllers by plugin for the site. Our new controllers should be listed without checks in the boxes. Add BOTH the websiteAdmin controller AND the guestbookAdmin controller and save the changes.

Now click "rebuild". This will require that the controllerMap.xml file is readable and writable by the webserver process - if not this step will error. On the warning, click OK to continue. You should then see a green success message.

Return to the control panel and a new section should appear listing guestbookAdmin. Go to it and you should see a typical DAO admin interface. Create an entry to test that all is working as it should. The interface of the form will likely not be very practical though, and perhaps you do not really want to be able to add items to the guestbook.

Configuring the templates

Back to your editor, find and open both the guestbookAdmin list and form templates. You will see that the object properties have been extracted and made available by default in these templates. Lets customise the list first by trimming down the number of columns.

Simply delete the entire line from within the foreach loop to stop it being displayed - remember to update the table headings!

Switch back to the web browser to see the changes.

Next the form, we should not see the primary key or edit it, so we need to update the form to hide it and to make some other changes such as making the comment a textarea. Customise as you see fit, but take note of the field names - any field that you remove should be added to the hidden section as input type="hidden".

When done, save the changes and check them out in the web browser.

Prevent creating entries

The final step of the admin process is to remove the "add" new entry functionality. For this we need to go back to the controller class file. Find the following section of code:

/**
 * @see mvcControllerBase::__construct()
 */
function __construct() {
	parent::__construct();
	
	$this->setSelectedMenuItem('controlPanel');
	$this->setControllerView('guestbookAdminView');
}

and add the following after it:

/**
 * @see mvcControllerBase::__construct()
 */
function __construct() {
	parent::__construct();
	
	$this->setSelectedMenuItem('controlPanel');
	$this->setControllerView('guestbookAdminView');
}

/**
 * @see mvcControllerBase::initialise()
 */
function initialise() {
	parent::initialise();
	
	$oItem = new mvcControllerMenuItem(self::ACTION_NEW, 'New', self::ACTION_NEW, 'Create a new record');
	$this->getMenuItems()->getItem(self::ACTION_VIEW)->removeItem($oItem);
}

Now save and reload the web browser - the ability to add an item has been removed.

And now the customer facing part

With the admin system pretty much sorted, we can now look at building our front-facing guestbook controller. For this tutorial I am going to add it to the base site for the sake of simplicity.

Back to the command line, and then run the following:

php scorpio.php new controller guestbook --site=base

This will create a new controller in the "base" site called "guestbook". We will need to add additional actions to allow for posting of new comments. Actions are always constants and should be set at the head of the controller class. There is already an existing action "view". Add under this an action for "post" and then another action for "doPost".

class guestbookController extends mvcController {
	const ACTION_VIEW = 'view';
	const ACTION_POST = 'post';
	const ACTION_DO_POST = 'doPost';
}

Now add these new actions to the set of permitted actions. This is done via the initialise() call. You can see the existing "view" action has already been assigned. The mvcControllerActions object supports a "fluent" interface (the object is returned after a set call) so you can chain the addAction() methods together:

$this->getControllerActions()
    ->addAction(self::ACTION_VIEW)
    ->addAction(self:ACTION_POST)
    ->addAction(self::ACTION_DO_POST);

Alternatively, add them individually if you prefer that syntax.

$this->getControllerActions()->addAction(self::ACTION_VIEW);
$this->getControllerActions()->addAction(self:ACTION_POST);
$this->getControllerActions()->addAction(self::ACTION_DO_POST);

In the main launch() method, logic needs to be added to handle a post and doPost request. For the sake of brevity the finished code looks something like the following. It is left to the reader to improve and understand what is here.

class guestbookController extends mvcController {
	
	const ACTION_VIEW = 'view';
	const ACTION_POST = 'post';
	const ACTION_DO_POST = 'doPost';

	/**
	 * @see mvcControllerBase::__construct()
	 */
	function __construct($inDefaultAction = self::ACTION_VIEW) {
		parent::__construct($inDefaultAction);
		
		$this->setRequiresAuthentication(false);
	}

	/**
	 * @see mvcControllerBase::initialise()
	 */
	function initialise() {
		parent::initialise();
		
		$this->getControllerActions()
			->addAction(self::ACTION_VIEW)
			->addAction(self::ACTION_POST)
			->addAction(self::ACTION_DO_POST);
			
		$this->addInputFilters();
	}
	/**
	 * @see mvcControllerBase::launch()
	 */
	function launch() {
		switch ( $this->getAction() ) {
			case self::ACTION_DO_POST:
				try {
					$data = $this->getInputManager()->doFilter();
					$this->addInputToModel($data, $this->getModel());
					
					$this->getModel()->save();
					header("Location: ".$this->buildUriPath(self::ACTION_VIEW));
					exit;
				} catch ( Exception $e ) {
					systemLog::error($e->getMessage());
					$this->setAction(self::ACTION_POST);
					$this->getModel()->setErrorMessage($e->getMessage());
				}
			break;
		}
		
		
		$oView = new guestbookView($this);
		if ( $this->getAction() == self::ACTION_POST ) {
			$oView->showGuestbookPostPage();
		} else {
			$oView->showGuestbookPage();
		}
	}

	/**
	 * @see mvcControllerBase::addInputFilters()
	 */
	function addInputFilters() {
		$this->getInputManager()->addFilter('Name', utilityInputFilter::filterString());
		$this->getInputManager()->addFilter('EmailAddress', utilityInputFilter::filterString());
		$this->getInputManager()->addFilter('Comment', utilityInputFilter::filterString());
	}
	
	/**
	 * @see mvcControllerBase::addInputToModel()
	 */
	function addInputToModel($inData, guestbookModel $inModel) {
		$inModel->setName($inData['Name']);
		$inModel->setEmailAddress($inData['EmailAddress']);
		$inModel->setComment($inData['Comment']);
		$inModel->setIpAddress($_SERVER['REMOTE_ADDR']);
		$inModel->setReferrer($_SERVER['HTTP_REFERER']);
	}
}

The generated model has been changed slightly. It now extends from the guestbook object, and adds an "error message" property:

class guestbookModel extends guestbook {
	
	protected $_ErrorMessage = '';
	
	function getErrorMessage() {
		return $this->_ErrorMessage;
	}
	
	function setErrorMessage($inError) {
		$this->_ErrorMessage = $inError;
		return $this;
	}
}

Finally the view needs assignment of data from the model:

class guestbookView extends mvcView {

	/**
	 * @see mvcViewBase::__construct()
	 */
	function __construct($inController) {
		parent::__construct($inController) ;
	
	}
	
	/**
	 * Shows the guestbookView page
	 *
	 * @return void
	 */
	function showGuestbookPage() {
		$this->setCacheLevelNone();
		
		$this->getEngine()->assign('entries', utilityOutputWrapper::wrap(guestbook::listOfObjects()));
		$this->getEngine()->assign('postURI', $this->getController()->buildUriPath(guestbookController::ACTION_POST));
		$this->render($this->getTpl('guestbook'));
	}
	
	/**
	 * Shows the guestbookPost page
	 *
	 * @return void
	 */
	function showGuestbookPostPage() {
		$this->setCacheLevelNone();
		$this->getEngine()->assign('oModel', utilityOutputWrapper::wrap($this->getModel()));
		$this->getEngine()->assign('doPostURI', $this->getController()->buildUriPath(guestbookController::ACTION_DO_POST));
		$this->render($this->getTpl('postMessage'));
	}
}

This should cover most of the logic needed to support the guestbook. The last elements are the two templates that we need to show entries and to allow a new entry to be created.

Customer facing templates

A template for the default view has already been created. This is going to be customised very simply to just output the entries using "print_r" (it is an exercise for the reader to make a decent layout).



	
		
		Scorpio Framework - {$oMap->getDescription()}
		
	

	
		

guestbook

{$entries|printr}

Post

And the form:



	
		
		Scorpio Framework - {$oMap->getDescription()}
		
	

	
		

post to guestbook

{if $oModel->getErrorMessage()}
Error: {$oModel->getErrorMessage()}
{/if}
Name
Email Address
Comment
 

Now all that is left to do is update the controllerMap file in the base site or dev.base site so that you can access the guestbook and to then preview it in your browser.

You should be able to list and then post to the guestbook, and had the error messages echo'd out to the posting page.

This is all very simple and a lot more could be done (e.g. make it using ajax, better error checking etc.) but this should give a good idea of how you can very rapidly prototype components for a site and how you can re-use the logic in different contexts.

Tutorial Config Files

The following are the config files that were used during this tutorial. They may be of use to you if you find you are having trouble.

Apache vhosts.conf

NameVirtualHost *:80

# default

        CustomLog logs/access.log combined


# framework directory

        Options Indexes FollowSymLinks
        AllowOverride All


# base

        DocumentRoot /home/dave/Documents/Scorpio/websites/base
        ServerName dev.base
        CustomLog logs/base.log combined


# baseAdminSite

        DocumentRoot /home/dave/Documents/Scorpio/websites/base
        ServerName dev.baseAdminSite
        CustomLog logs/baseAdminSite.log combined

dev.base and dev.baseAdminSite config files

// dev.base

        
//dev.baseAdminSite

ControllerMap Files

Download the basic files as a Zip file.

< Return to article