Stupidly Easy App Walkthough

One day, the phone rings.
Joe: Hey!! can you make a website for my Candy Wrapper Club?
You: "Candy Wrapper Club?!?"
Joe: "Yes! We collect Candy Wrappers! We just need a little bit about the group, how to contact us and a membership list. It will probably only take you a week. My cousin has a webserver with PHP and MySQL. How about it?
You: <sigh> Sureeeeeeee, I've been wanting to try this MVC thing with PHP.
This is a long one, so grab a cup of coffee!

A little planning never hurt anyone so you make a list of the pages needed:

  • main
  • contact us
  • member list
  • about

Not too bad. You decide to do the easy part first. The main, contact us and about will require no database interaction. We can work on this while Joe is getting the server information. We will probably need two controllers, one to display the site and one for admin. Maybe another one for the membership list, lets worry about that part later.

First we create the index.php page, which will instantiate the Site Controller:

define("SITE_ROOT", $_SERVER['DOCUMENT_ROOT'] . "/simple/");

include_once(SITE_ROOT .'controllers/Site_Controller.class.php');

$site = new Site_Controller();

Since this site isn't on the htdocs path, I set a constant with the path to the "root" of this site. I don't know the setup that Joe has, this way I'm covered. If the site does end up going in the htdocs directory I can just remove the "/simple/" out of the site_root and it would still work.

Then we need to make the site controller, this determines what pages to display.

include_once(SITE_ROOT . "lib/mvc/MVC_Controller.class.php");
include_once(SITE_ROOT . "views/Site_View.class.php");

class Site_Controller extends MVC_Controller {
    function Site_Controller()
    {
        parent::MVC_Controller();
        $this->view = new Site_View();
        $this->do_action();
    }

    function default_action() {
        $this->index();
    }

    function index()
    {
        $this->view->display('index');
    }

    function about()
    {
        $this->view->display('about');
    }

    function contact()
    {
        $this->view->display('contact');
    }

}

Note: in RoR, the ActionController, the default view is displaying a template of the same name as the method. I don't know if such automagicification is possible with PHP, but the wheels are turning in the back of my brain (What? you don't have wheels in the back of your brain?).

Lets take a close look at the Constructor:

   function Site_Controller()
    {
        parent::MVC_Controller();
        $this->view = new Site_View();
        $this->do_action();
    }

First, we call the parents constructor. Then we need to instantiate the view object. We must call do_action() to "act" on the $_REQUEST['action'] variable, which is how we know what to do.

THIS IS THE KEY!

    function do_action() {
        $action = $_REQUEST['action'];
        print "Action: $action";
        if (empty($action)) {
            $this->default_action($params);
        }
        else if (method_exists($this, $action)) {
            call_user_func(array(&$this, $action), $params);
        }
        else {
            $this->nonexisting_action($action);
        }
    }

(experimenting with not hugging my elses, as I've learned from Perl's Best Practices)

First, we get the action variable. If it is empty, we call "default_action" which in the base class just prints out "This is the default action" because you should override that method in your controller. If its NOT empty, we see if there exists a method with that name (in site controller we have methods "index","about","contact"). If there doesn't exist a method, an error message is printed. You might not have implemented that method yet or the user tampered with the REQUEST variable (I can't see what harm the evil hacker could do, other than trigger that error, maybe Chris Shifflet will see this and tell if there's anything to worry about, but I can't think of anything. The user could enter the name of another action, but if you have an action delete_all_data without further confirmation variables, then well you have bigger problems than this.)

Take a look at the menu for our site (partial table code), this goes in the layout files (more on that in a second):

    <td><a href='index.php?action=index'>Home</a></td>
    <td><a href='index.php?action=list'>Membership List</a></td>
    <td><a href='index.php?action=about'>About</a></td>
    <td><a href='index.php?action=contact'>Contact Us</a></td>

I suppose if you were gifted with the knowledge of mod_rewrite you could make these "pretty urls" but these work for me. I'm not picky.

On to the view. I have modified my MVC framework a bit, after having worked with RoR's structure a bit more. Here's the view file:

include_once(SITE_ROOT . "lib/mvc/MVC_View.class.php");

class Site_View extends MVC_View {
    function Site_View()
    {
        parent::MVC_View();
        $this->set_layout('site');
        $this->set_controller('site');
    }
}

This is something new, I'm setting the name of the Layout and the Controller. In RoR, the view directory is laid out like this:

   --views
      |--- layouts
      |--- [controller1]
      |--- [controller2]
      |--- [controller3]

Where [controller1] is the name of a controller, etc. A controller has multiple views, like we saw with the Site Controller above, we have index, about, and contact. The layouts directory contains, well layouts. A layout is the things that are the same on each page: logo, header, menu, etc. So I made a simple layout and named it "site.tpl.php" and put in the layouts directory. Now, in this template file is a variable named "content_for_layout" and thats where the page specific content goes. In the index view, it would contain a basic description of the site, in the about section it would contain a bit about the group and contact would contain names, emails and phone numbers. Nothing about the menu or logo display or anything would be in this, just content specific to that page.

Lets see some templates:
layout.tpl.php

<html>
<head>
<title>Bradley Candy Wrapper Club</title>

</head>
<body>
<table border='0' cellpadding='5'>
<tr>
    <td><a href='index.php?action=index'>Home</a></td>
    <td><a href='index.php?action=list'>Membership List</a></td>
    <td><a href='index.php?action=about'>About</a></td>
    <td><a href='index.php?action=contact'>Contact Us</a></td>
</tr>
</table>


<div id='content' width='300'>

<?=$content_for_layout;?>

</div>

</body>
</html>

Then for index.tpl.php:

<p>
Welcome to the Bradley Chapter of The Candy Wrapper club! We are glad you are
here to learn more about the joys of collecting candy wrappers. Collecting
candy wrappers is a great hobby. It is inexpensive, easily stored in a shoebox
and best of all, you get to taste all kinds of candy!
</p>
<p>
On this site you will learn...
<ul>
<li>The care and cleaning of your candy wrappers
so generations to come will be able to see the various candy wrappers you loved.
<li>See names and email addresses of other candy wrapper collectors so you can
find people to swap candy wrappers with!
<li>More about the history of candy wrappers
</ul>
</p>

You can use any template you like, in previous examples I've used smarty. I've even used straight HTML in methods in the view, but I am not sure that approach would work here with my dividing up of things in layout. So in the effort of simplicity, I created my own template class (a topic for a future article).

So you can see the reason I had to give my Site_View class the name of the layout (to get the proper layout from the layouts/ directory) and the name of the controller (to locate the directory where the templates where). In RoR, this happens automagically. I am not sure I can implement the same in PHP. My goal is NOT to implement all of RoR in PHP, but if I can take some inspiration from RoR, I dont think they will mind. I'm not in a position to use Ruby on my day job, so I can have a bit of fun like the cool kids.

Next article, will answer the question but what if you have MORE things on the page? like a login box, last articles, what's new along with the page content? .. and we'll finish the basics and I'll provide a link to download the application (so you can set it up for your local Candy Wrapper Chapter!). And find out where the heck a model comes into all this

in that case

you don't need PHP at all.

In terms of clarity...

While I understand the need to ensure that user input is clean, resetting $_GET values seems like a very unclear way of doing it. When I first looked at this, I thought "Wow, this guy is directly using input without scrubbing it?"

Then again, as Nola notes, you can replace all of this with a simple link instead of using any programming at all.