Difference between revisions of "6CC001 Workshop - week 04"

From mi-linux
Jump to navigationJump to search
Line 242: Line 242:
 
</pre>
 
</pre>
  
Our parent Command class:
+
Now that we have our Front Controller and Command Resolver, let’s create our first command!
 +
 
 +
First, we need an abstract Command class, from which all our commands will derive. It acts as an interface contract, and says “any class that wishes to be a Command must extend me and implement a doExecute() method.
 +
 
 +
Here is the code:
  
 
<pre>
 
<pre>

Revision as of 16:36, 27 July 2009

Main Page >> Web Application Development >> Workbook >> Week 04

Step 1 - Create and populate database

First, let’s create a very simple table to store our blog messages.

The following SQL code will create the table and populate it with a couple of dummy records. Simply paste it and run it in phpmyadmin.

CREATE TABLE IF NOT EXISTS `messages` (
  `id` int(11) NOT NULL auto_increment,
  `title` varchar(255) NOT NULL,
  `message` text NOT NULL,
  `date_added` datetime NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=19 ;

INSERT INTO `messages` (`id`, `title`, `message`, `date_added`) VALUES
(1, 'Test 1', 'Hello world!', '2009-07-27 15:06:29'),
(2, 'Test 2', 'Hiya', '2009-07-27 15:06:38');

Step 2 - Create the index.php file, front controller and registry class

As explained in the lecture, our application has a single entry point that handles all user requests: index.php

Well, index.php doesn’t really do much. It simply creates and runs the Front Controller object:

// The index simply calls the front controller
require("Controller.php");
Controller::run();

Next, let's have a look at the Controller class itself.

Index.php calls the run function, which creates an instance of the Front Controller, and then runs a couple of internal functions:

  • init() does some initial set-up. In our case, it saves the database details to the registry.
  • handleRequest() does the actual work. Let’s keep it simple for now.
require_once("Registry.php");

//############################################################################
// Front Controller class
//############################################################################
class Controller
{
  private function __construct(){}
  
  //############################################################################
  // Main "run" function
  //############################################################################    
  static function run()
  {
    $instance = new Controller;
    $instance->init();
    $instance->handleRequest();
  }
  
  //############################################################################
  // Init config stuff, e.g. database location, etc.
  //############################################################################   
  function init()
  {
    Registry::set("database_dsn", "mysql:host=localhost;dbname=YOURDBNAME");
    Registry::set("database_login", "YOURUSER");
    Registry::set("database_password", "YOURPASSWORD");
  }
  
  //############################################################################
  // Handles request from user
  //############################################################################   
  function handleRequest()
  {
    print("Hello world!");
  }
}

The Registry class is a simple front-end for an array. You can pass it values using the set() function, and retrieve them later using the get() function.

//############################################################################
// Registry class, used to store values to be used across whole application
//############################################################################
class Registry
{
  private static $values = array();
  
  //############################################################################
  // Store a value
  //############################################################################    
  static function set($key, $val)
  {
    self::$values[$key] = $val;
  }
  
  //############################################################################
  // Get a value
  //############################################################################    
  static function get($key)
  {
    if(isset(self::$values[$key]))
    {
      return self::$values[$key];
    }
    return null;
  }
  
}

Checkpoint

It is still early days!

If you browse to the location of your index.php file, you should see "Hello world!". It doesn’t seem much, but we are already creating our Front Controller, and getting it to save our database connection details to the Registry.

We now need to handle the actual user request, i.e. display the page that the user asked for.

Step 3 - Handling user request and created appropriate command

When you create a Request object, it simply populates itself with all the values contained in the $_REQUEST PHP super global variable. So anything passed by the user via $_POST, $_GET etc. will be stored in this object.

You can access the values above via the getProperty function.

//############################################################################
// Request class
//############################################################################
class Request
{
  private $properties;

  //############################################################################
  // constructor
  //############################################################################ 
  function __construct()
  {
    $this->init();
  }
  
  //############################################################################
  // Gets all parameters from GET, POST etc...
  //############################################################################   
  function init()
  {
    $this->properties = $_REQUEST;
    return;
  }
  
  //############################################################################
  // Functions to "set" and "get" properties
  //############################################################################   
  function getProperty($key)
  {
    if(isset($this->properties[$key]))
    {
      return $this->properties[$key];
    }
  }
  function setProperty($key, $val)
  {
    $this->properties[$key] = $val;
  }
}

Another very important tool needed by our Front Controller is the Command Resolver.

Put simply, it works like this: If the user types “index.php?cmd=BlogAddForm” in the address bar, then the command resolver will create and return a “Command_BlogAddForm” object.

It does a bit more than that, obviously. It first checks that the class exists, and that it is a valid class (i.e. it extends the Command object). If something goes wrong, it returns a default Command (Command_BlogIndex in our case).

require_once("Command.php");
require_once("Command_BlogIndex.php");

//############################################################################
// CommandResolver class
//############################################################################
class CommandResolver
{
  private static $base_cmd;
  private static $default_cmd;
  
  //############################################################################
  // Constructor
  //############################################################################    
  function __construct()
  {
    if(!self::$base_cmd)
    {
      // To check if requested command is a valid command
      self::$base_cmd = new ReflectionClass("Command");
      
      // Default command to be returned if an error occurs
      self::$default_cmd = new Command_BlogIndex();
    }
  }
  
  //############################################################################
  // getCommand creates the appropriate command class, 
  // depending on parameter passed in URL
  //############################################################################    
  function getCommand(Request $request)
  {
    // Get "cmd" parameter from request
    $cmd = $request->getProperty('cmd');

    // If no command passed in, use default one    
    if(!$cmd)
      return self::$default_cmd;
      
    $filepath = "Command_{$cmd}.php";
    $classname = "Command_{$cmd}";
    
    // Check if file containing command class exists
    if(file_exists($filepath))
    {
      require_once($filepath);
      
      // Check if command class exists
      if(class_exists($classname))
      {
        $cmd_class = new ReflectionClass($classname);
        
        // Check of class is a valid command
        if($cmd_class->isSubClassOf(self::$base_cmd))
        {
          // Return new instance of command
          return $cmd_class->newInstance();
        }
      }
    }
    
    // Couldn't find command, return default command
    return clone self::$default_cmd;
  }
}

Now that we have our Front Controller and Command Resolver, let’s create our first command!

First, we need an abstract Command class, from which all our commands will derive. It acts as an interface contract, and says “any class that wishes to be a Command must extend me and implement a doExecute() method.

Here is the code:

//############################################################################
// Command class
//############################################################################
abstract class Command
{
  // Make constructor final so sub-classes can't add parameters to it
  final function __construct(){}
  
  //############################################################################
  // Execute command
  //############################################################################   
  function execute(Request $request)
  {
    // General setup for all commands
    // ...
    
    // Then call specific command code
    $this->doExecute($request);
  }
  
  // Specific command code, to be implemented by each sub-class
  abstract function doExecute(Request $request);
}

And our first command: the Command_BlogIndex class

//############################################################################
// SearchVenueCommand class
//############################################################################
class Command_BlogIndex extends Command
{
  //############################################################################
  // doExecute
  //############################################################################ 
  function doExecute(Request $request)
  {
    print("Blog Index command");
  }
}

Let's update our Controller class. We now read the request from the user, and use the CommandResolver object to create a Command.

Add these at the top of the file:

require_once("Request.php");
require_once("CommandResolver.php");

And implement the handleRequest function:

  //############################################################################
  // Handles request from user
  //############################################################################   
  function handleRequest()
  {
    // Create object that handles request from user
    $request = new Request();
    
    // Create object that decides which command to create depending on request
    $cmd_r = new CommandResolver();
    
    // This function return the appropriate command
    $cmd = $cmd_r->getCommand($request);
    
    // Execute command
    $cmd->execute($request);
  }

Checkpoint

Your website should now display "Blog Index command".

Step 4 - Accessing the database

First let's create our Manager class:

require_once("Registry.php");

//############################################################################
// Manager class
//############################################################################
abstract class Manager
{
  static $DB;
  static $stmts = array();
  
  //############################################################################
  // Constructor
  //############################################################################   
  function __construct()
  {
    // Get DB details from registry
    $database_dsn = Registry::get("database_dsn");
    $database_login = Registry::get("database_login");
    $database_password = Registry::get("database_password");
    
    // Create connection
    $pdo = new PDO($database_dsn, $database_login, $database_password);
    self::$DB = $pdo;

    // Set connection attributes    
    self::$DB->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  }

  //############################################################################
  // prepareStatement: prepares anc caches SQL statement
  //############################################################################   
  function prepareStatement($stmt_s)
  {
    // Check if statement is already in cache
    if(isset(self::$stmts[$stmt_s]))
    {
      return self::$stmts[$stmt_s];
    }
    
    // Prepare stement, store in cache and return
    $stmt_handle = self::$DB->prepare($stmt_s);
    self::$stmts[$stmt_s] = $stmt_handle;
    return $stmt_handle;
  }
  
  //############################################################################
  // doStatement: runs SQL statement
  //############################################################################   
  protected function doStatement($stmt_s, $values_a)
  {
    $sth = $this->prepareStatement($stmt_s);
    
    // Closes the cursor, enabling the statement to be executed again.
    $sth->closeCursor();
    
    // Runs the statement
    $db_result = $sth->execute($values_a);
    return $sth;
  }
}

Next, our ManagerMessage class:

require_once("Manager.php");

//############################################################################
// ManagerMessage class
//############################################################################
class ManagerMessage extends Manager
{
  static $add_message = "INSERT INTO messages(title,message,date_added) values(?,?,?)";
  static $list_messages = "SELECT id,title,message,date_added FROM messages ORDER BY date_added DESC";
  static $search_messages = "SELECT id,title,message,date_added FROM messages WHERE title LIKE ? ORDER BY date_added DESC";
  static $delete_message = "DELETE FROM messages WHERE id = ?";
  
  //############################################################################
  // addMessage: adds a message to the database
  //############################################################################ 
  function addMessage($title, $message)
  {
    $values = array($title, $message, date("Y/m/d H:i:s"));
    $this->doStatement(self::$add_message, $values);
  }
  
  //############################################################################
  // getAllMessages: selects all messages from the database
  //############################################################################ 
  function getAllMessages()
  {
    $stmt = $this->doStatement(self::$list_messages, null);
    $result = $stmt->fetchAll();
    return $result;
  }
  
  //############################################################################
  // searchMessages: searches for a message
  //############################################################################ 
  function searchMessages($keywords)
  {
    $values = array($keywords);
    $stmt = $this->doStatement(self::$search_messages, $values);
    $result = $stmt->fetchAll();
    return $result;
  }
  
  //############################################################################
  // deleteMessage: deletes a message
  //############################################################################ 
  function deleteMessage($id)
  {
    $values = array($id);
    $stmt = $this->doStatement(self::$delete_message, $values);
  }  
  
}
<pre>

Now we can implement the doExecute function in Command_BlogIndex, so it call our database classes:

<pre>
  //############################################################################
  // doExecute
  //############################################################################ 
  function doExecute(Request $request)
  {
    // Create manager object
    $manager = new ManagerMessage();
    
    // Are we searching?
    $keywords = trim($request->getProperty('search'));
    
    if($keywords=="")
      $data = $manager->getAllMessages();
    else
      $data = $manager->searchMessages($keywords);
      
    print_r($data);
  }

Checkpoint

Your website should now be displaying the data from the database in the form an an array:

Array ( [0] => Array ( [id] => 18 [0] => 18 [title] => Test 2 [1] => Test 2 [message] => Hiya [2] => Hiya [date_added] => 2009-07-27 15:06:38 [3] => 2009-07-27 15:06:38 ) [1] => Array ( [id] => 17 [0] => 17 [title] => Test 1 [1] => Test 1 [message] => Hello world! [2] => Hello world! [date_added] => 2009-07-27 15:06:29 [3] => 2009-07-27 15:06:29 ) )

Step 5 - Create our first view

All we need now is to pass the data above to a view. Let's create all our views in a "views" sub-folder.

<?require_once("view_helper.php");?>

<?ViewHelper::DisplayHeader("Blog index");?>

<h1>Blog index</h1>

<p>You can <a href="index.php?cmd=BlogAddForm">add messages</a> !</p>

<form action="index.php" method="post">
  Search: <input name="search"> 
  <input type="submit" value="Search!"> 
</form>

<?
  foreach($data as $message)
    echo("<div class=\"message\">
            <h2>{$message['title']}</h2>
            <p>{$message['message']}</p>
            <a href=\"index.php?cmd=BlogDelete&id={$message['id']}\">Delete this message</a>
          </div>");
?>

<?ViewHelper::DisplayFooter();?>

As you can see, we have chosen to use a helper:

//############################################################################
// ViewHelper
//############################################################################
class ViewHelper
{
  //############################################################################
  // Displays HTML Header
  //############################################################################ 
  static function DisplayHeader($pageTitle = "")
  {
    echo("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">");
    echo("<html>");
    echo("  <head>");
    echo("    <title>{$pageTitle}</title>");
    echo("    <link type=\"text/css\" href=\"views/style.css\" rel=\"stylesheet\">");
    echo("  </head>");
    echo("  <body>");
  }
  
  //############################################################################
  // Displays HTML Footer
  //############################################################################   
  static function DisplayFooter()
  {
    echo("  </body>");
    echo("</html>");  
  }
}

All you need now is to include your view from your command:

  //############################################################################
  // doExecute
  //############################################################################ 
  function doExecute(Request $request)
  {
    // Create manager object
    $manager = new ManagerMessage();
    
    // Are we searching?
    $keywords = trim($request->getProperty('search'));
    
    if($keywords=="")
      $data = $manager->getAllMessages();
    else
      $data = $manager->searchMessages($keywords);
      
    // Include view
    include("views/index.php");
  }

Checkpoint

The home page of your blog should now be working:

Blog.gif

Step 6 - Adding messages

//############################################################################
// SearchVenueCommand class
//############################################################################
class Command_BlogAddForm extends Command
{
  //############################################################################
  // doExecute
  //############################################################################ 
  function doExecute(Request $request)
  {
    // Include view
    include("views/add_form.php");
  }
}
<?require_once("view_helper.php");?>

<?ViewHelper::DisplayHeader("Add message");?>

<h1>Blog add form</h1>

<p>Please fill in the fields</p>

<form action="index.php?cmd=BlogAdd" method="post">
  Title:<br><input name="title">
  <br>
  Message:<br><textarea name="message"></textarea>
  <br>
  <input type="submit" value="Add">
</form>
    
<?ViewHelper::DisplayFooter();?>
//############################################################################
// Command_BlogAdd class
//############################################################################
class Command_BlogAdd extends Command
{
  //############################################################################
  // doExecute
  //############################################################################ 
  function doExecute(Request $request)
  {
    // Get data from request
    $title = $request->getProperty('title');
    $message = $request->getProperty('message');
    
    // Create manager object
    $manager = new ManagerMessage();    
    
    // Add to database
    $manager->addMessage($title, $message);
    
    // Redirect to index
    header("location:index.php");
  }
}

Step 7 - Deleting messages

//############################################################################
// SearchVenueCommand class
//############################################################################
class Command_BlogDelete extends Command
{
  //############################################################################
  // doExecute
  //############################################################################ 
  function doExecute(Request $request)
  {
    // Get data from request
    $id = $request->getProperty('id');
    
    // Create manager object
    $manager = new ManagerMessage();    
    
    // Add to database
    $manager->deleteMessage($id);
    
    // Redirect to index
    header("location:index.php");
  }
}

Step 8 - Searching for message

You can do this one :)