Front controller by Corbyn, where to put website wrapper?

Not for 'how-to' coding questions but PHP theory instead, this forum is here for those of us who wish to learn about design aspects of programming with PHP.

Moderator: General Moderators

kilbad
Forum Commoner
Posts: 28
Joined: Wed Apr 02, 2008 3:51 pm

Front controller by Corbyn, where to put website wrapper?

Post by kilbad »

~pickle | Please use [ code=html ], [ code=php ], etc tags where appropriate when posting code. Your post has been edited to reflect how we'd like it posted. Please read: :arrow: Posting Code in the Forums to learn how to do it too.


I have been working through an excellent OOP front controller tutorial by Chris Corbyn, found here, but have two questions:

(1) Currently, all my "content" is outputted by the ActionController.php5 displayView() function, which I "wrap" in xhtml header/footer file(s) that I am including in my index.php5 (mainly because I don't know where best to include them). Therefore, my question is, where should I be including my xhtml header/footer file(s)? My only goals are to (a) keep it simple (i.e. I am not looking to build/implement a templating system), and (b) keep my presentation logic separate from my business logic.

(2) How can I improve my exception throwing and catching in the FrontController.php5? Again, I want to keep it simple, but still want to make sure I am handling my exceptions appropriately.

I am new to OOP/php, and so I appreciate any feedback you have. Thank you all in advance!

Sincerely,
Brendan



Here are all my files:

Index.php5

Code: Select all

 
<?php
 
//Setting full error reporting
ini_set ("display_errors", "1");
error_reporting(E_ALL);
 
//Setting constant containing root directory for this application
define("PAGE_DIR", "includes/php");
 
//Initializing session
session_start();
 
//Including general configuration and function files
require_once PAGE_DIR . "/Configurations.php5";
require_once PAGE_DIR . "/Functions.php5";
 
//Including header template file(s)
require_once PAGE_DIR . "/template/pre.header.inc.php5";
require_once PAGE_DIR . "/template/header.inc.php5";
require_once PAGE_DIR . "/template/post.header.inc.php5";
 
 
////FRONT CONTROLLER////////////////////////////////////////////////////////////////////////////
 
//Requiring the front controller class
require_once PAGE_DIR . "/FrontController.php5";
FrontController::createInstance(PAGE_DIR)->dispatch(TRUE);
 
////////////////////////////////////////////////////////////////////////////////////////////////
 
//Including footer template file(s)
require_once PAGE_DIR . "/template/footer.inc.php5";
 
?>
 

FrontController.php5

Code: Select all

 
<?php
 
/*  Reference(s)
 
    "A lightweight and flexible front controller for PHP 5" by Chris Corbyn
    http://www.w3style.co.uk/a-lightweight- ... -for-php-5
 
*/
 
require_once PAGE_DIR . "/ActionController.php5";
 
class FrontController {
 
    private static $pageDir;
 
    public static function createInstance($pageDir){
        self::$pageDir = $pageDir;
        
        $instance = new self();
        return $instance;
    }
 
    public function dispatch($throwExceptions = false) {
 
        //The next two lines are ternary operators, a sort of short-hand for if-else
        $module= isset($_GET["module"]) ? filter_input(INPUT_GET, 'module', FILTER_SANITIZE_STRING) : "home";
        $action = isset($_GET["action"]) ? filter_input(INPUT_GET, 'action', FILTER_SANITIZE_STRING) : "frontpage";
 
        //example GuestbookActions
        $class = ucfirst($module) . "Actions";
 
        //e.g. guestbook/GuestbookActions.php5
        $file = self::$pageDir . "/" . $module . "/" . $class . ".php5";
        if (!is_file($file)) {
            throw new FrontControllerException("Page not found!");
        }
 
        //Requiring the Actions file
        require_once $file;
 
        //Creating a new object
        $controller = new $class();
 
        //Passing page directory to the ActionController class
        $controller->setPageDir(self::$pageDir);
 
        try {
            //Using the setName variable from the ActionController class in ActionController.php5
            $controller->setName($module);
 
            //Checks if the method exists, then runs the displayView action
            $controller->dispatchAction($action);
        }
        catch(Exception $e){
            //An exception has occurred
            if($throwExceptions){
                    echo $e->errorMessage();
                }
        }
 
    }
}   
 
class FrontControllerException extends Exception {
    public function errorMessage(){
        //Error message
        $errorMsg = $this->getMessage() . ' Error on line ' . $this->getLine() . ' in ' . $this->getFile();
        return $errorMsg;
    }
}
 
?>
 

ActionController.php5

Code: Select all

 
<?php
 
abstract class ActionController {
 
    protected $name;
    protected $viewData = array();
 
    private static $pageDir;
 
    public static function setPageDir($pageDir){
        self::$pageDir = $pageDir;
    }
 
    public function setName($name) {
        $this->name = $name;
    }
 
    public function getName() {
        return $this->name;
    }
 
    //This function places a value in the $viewData array at the postion indicated by the key
    public function setVar($key, $value) {
        $this->viewData[$key] = $value;
    }
 
    //This returns a value from the array located at the key value
    public function getVar($key) {
        if (array_key_exists($key, $this->viewData)) {
            return $this->viewData[$key];
        }
    }
 
    //method_exists checks if the class method exists in the given object, then runs the displayView function if exists
    public function dispatchAction($action) {
        $actionMethod = "do" . ucfirst($action);
        if (!method_exists($this, $actionMethod)) {
            throw new FrontControllerException("Page not found!");
        }
 
        $this->$actionMethod();
        $this->displayView($action);
    }
 
    public function displayView($action) {
        if (!is_file(self::$pageDir . "/" . $this->getName() . "/" . $action . "View.php5")) {
            throw new FrontControllerException("Page not found!");
        }
 
        //Create variables for the template
        foreach ($this->viewData as $key => $value) {
            $$key = $value;
        }
 
        include PAGE_DIR . "/" . $this->getName() . "/" . $action . "View.php5";
    }
 
    public function __set($key, $value)  {
        $this->setVar($key, $value);
    }
 
    public function __get($key) {
        return $this->getVar($key);
    }
    
}
 
?>
 


Thanks again!


~pickle | Please use [ code=html ], [ code=php ], etc tags where appropriate when posting code. Your post has been edited to reflect how we'd like it posted. Please read: :arrow: Posting Code in the Forums to learn how to do it too.
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Re: Front controller by Corbyn, where to put website wrapper?

Post by Chris Corbyn »

(1). First things first, I'd probably move your includes for header and footer into the view for each page itself. This is a very basic (and manageable) composite view.

someActionView.php

Code: Select all

<?php include "header.php"; ?>
 
Yadda yadda here's my content
 
<?php include "footer.php"; ?>
 
We use this technique at work. Admittedly it's a bit repetitive. But in your current implementation, what if you want to provide some variables to those includes the way you have them? Title, meta tags, stylesheets etc?

If you want some abstraction to avoid that repetition use a Response object then have your FrontController use output buffering to capture the HTML from the action into a string before loading the "layout" view.

This is incredibly pseudo since I've just back from a night out in the city and not really in the state of mind to be writing anything too meaningful

Code: Select all

ob_start();
include "templates/" . $action . "View.php";
$html = ob_get_clean();
 
$response->setContent($html);
 
include "templates/layout.php";
Where layout.php looks something like:

Code: Select all

<html>
  <head>
    <title><?php echo $title; ?></title>
  </head>
  <body>
    <?php echo $response->getContent(); ?>
  </body>
</html>
(2). Catching Exceptions is something I should probably go more into. You probably want your catch-all solution like you have, but be aware of a special types of Exception before you use it. For example, I throw PageNotFoundException() in order to display a 404 error page (such as when a primary key in the URL cannot be found in the DB), and a AccessException to display a login page. In the simplest case place include() in your catch() block rather than echoing content. This way you can at least include a pretty template. You can be very clever with your front controller and have it forward() to an action specified to handle 404's or logins though. If you're going to add a call to forward() in your catch() block then I'd say to make it a config option to specify what action to take in certain Exception scenarios.

Basic includes (pseudo):

Code: Select all

try {
  $page->$theAction();
  //... Other logic ...
} catch (PageNotFoundException $e) {
  include "templates/404.php";
} catch (AccessException $e) {
  include "login.php";
} catch (Exception $e) {
  include "error.php";
}
Using forward().

Code: Select all

try {
  $page->$theAction();
  //... Other logic ...
} catch (PageNotFoundException $e) {
  $this->forward($pageNotFoundController, $pageNotFoundAction);
} catch (AccessException $e) {
  $this->forward($loginController, $loginAction);
} catch (Exception $e) {
  $this->forward($errorController, $errorAction);
}
I'll provide a better response when I'm more sober sorry :oops: I was actually thinking of creating a PHP Tutorials wiki covering some general concepts like this then getting a few people from here to contribute. I don't have much free time lately though :(
User avatar
RobertGonzalez
Site Administrator
Posts: 14293
Joined: Tue Sep 09, 2003 6:04 pm
Location: Fremont, CA, USA

Re: Front controller by Corbyn, where to put website wrapper?

Post by RobertGonzalez »

Typically I create a layout feature that incorporates several components in a single view and feeds the Module/Action view into that layout so there are no includes anywhere. But that is probably more robust than you want to get at right now.

The general progression of an MVC (or at least the VC part of it) will go something like...
  • The Front Controller starts the ball rolling by asking the Request Object to ask the URI object what the client is asking for
  • The Request Object asks the URI object what the client is asking for
  • The URI object tells the Request object what the client wants
  • The Request Object object tells the Front Controller what the client is asking for (Page/Module and Action)
  • The Front Controller tells the View Object the Page/Module and Action requested and prepares the needed Action view
  • The Front Controller then forwards control of the process to the requested Page Controller (typically while calling the Action method of the Page controller)
  • The Action method of the Page Controller is where the magic happens but where nothing is output...
  • The Front Controller then receives control back from the Page Controller where it then asks the View Object for the action view template
  • The Front Controller then parses (or includes) the action view template, taking the variables set in the Page Controller Action Method and reading them into the template.
Of course, I learned most of this from Chris in a small rundown tutorial he wrote almost a year ago. :wink:
kilbad
Forum Commoner
Posts: 28
Joined: Wed Apr 02, 2008 3:51 pm

Re: Front controller by Corbyn, where to put website wrapper?

Post by kilbad »

First, let me apologize for not placing this thread in the appropriate forum. I will sincerely try not to let that happen again.

Second, thank you both for your feedback. However, even after reading your posts, I guess I am still struggling conceptually with two things (1) keeping my business logic separate from my presentation logic, but also (2) keep my code from becoming redundant (i.e. using the same code in multiple places). Therefore, @Chris, I know you were out drinking (and nobody loves a good time more than me :drunk: ) so your examples were more pseudo, but would you (or anyone for that matter) be willing to take the code I initially posted above and use it to give me a concrete example of what you would do with my xhtml header/footer files?

Restated, I would like to know how to take my code above, and
Everah wrote: ...create a layout feature that incorporates several components in a single view and feeds the Module/Action view into that layout so there are no includes anywhere.
Regardless, thank you both for all your help so far!

Sincerely,
bt
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: Front controller by Corbyn, where to put website wrapper?

Post by Christopher »

Everah wrote:[*]The Front Controller tells the View Object the Page/Module and Action requested and prepares the needed Action view
[*]The Front Controller then forwards control of the process to the requested Page Controller (typically while calling the Action method of the Page controller)
[*]The Action method of the Page Controller is where the magic happens but where nothing is output...
[*]The Front Controller then receives control back from the Page Controller where it then asks the View Object for the action view template
[*]The Front Controller then parses (or includes) the action view template, taking the variables set in the Page Controller Action Method and reading them into the template.[/list]
I have not read that article in a long time, but does it really get the Front Controller involved with the View? That is, in my opinion, not a concern of a Front Controller. I also think you are using the term "Page Controller" to mean "Action Controller".
(#10850)
User avatar
RobertGonzalez
Site Administrator
Posts: 14293
Joined: Tue Sep 09, 2003 6:04 pm
Location: Fremont, CA, USA

Re: Front controller by Corbyn, where to put website wrapper?

Post by RobertGonzalez »

His article might not. I think mine might.

It has been a while since I have been in my controller code, so that may not be entirely the steps that mine follows.
User avatar
RobertGonzalez
Site Administrator
Posts: 14293
Joined: Tue Sep 09, 2003 6:04 pm
Location: Fremont, CA, USA

Re: Front controller by Corbyn, where to put website wrapper?

Post by RobertGonzalez »

arborint, you were right. The view object is handled inside the page controller for me, not the front controller.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: Front controller by Corbyn, where to put website wrapper?

Post by Christopher »

Yes, or the other option is to deal with it after the FC runs using a Response object.
(#10850)
blueyon
Forum Commoner
Posts: 76
Joined: Tue Oct 30, 2007 9:53 am

Re: Front controller by Corbyn, where to put website wrapper?

Post by blueyon »

I don't like how the tutorial handles forwards.

I prefer how the skeleton framework does it. It passes a action back to the front controller.

I think the devnetwork forum should write a tutorial of how to setup a basic MVC structure. It should include things like:

forward method
redirect method
render method
composite views
calling models

Some other things that get to me are how to handle pre and post methods on some pages and not others. how to decare what child controllers are to be loaded with different pages.

I thnk this sort of thing can be done with config files that are loaded at the start of the script. Your could even include where to get resources from.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: Front controller by Corbyn, where to put website wrapper?

Post by Christopher »

Start a thread and we can start the discussion. If there is consensus on an idea I will add it to the first post in the thread to build up a document.
(#10850)
blueyon
Forum Commoner
Posts: 76
Joined: Tue Oct 30, 2007 9:53 am

Re: Front controller by Corbyn, where to put website wrapper?

Post by blueyon »

User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Re: Front controller by Corbyn, where to put website wrapper?

Post by Chris Corbyn »

blueyon wrote:I don't like how the tutorial handles forwards.
I'm not 100% sure I like the implementation these days. I was trying to use an API similar to that of symfony's forward() mechanism. I'd agree that returning a new action would be easier to read since you'd see the exit point more clearly. Everytime I come back to this topic I have new ideas on how to do things.
blueyon wrote:I think the devnetwork forum should write a tutorial of how to setup a basic MVC structure.
Great idea. I was just thinking today about creating a wiki somewhere and building it up like a book with contributions from members here. A bit like my failed attempt to write a community book but with more realistic initial expectations (i.e. a few tutorials in a wiki). Could even be a wiki under the devnetwork.net domain :)
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Re: Front controller by Corbyn, where to put website wrapper?

Post by Chris Corbyn »

With regard to dealing with your composite view it would be a good idea to start separating VC a little more. Creating a View class would be a good initial start. I'm just typing this in the thread as I go so it's untested. What I'm basically doing here is delegating all the calls to things which relate to the View (setVar, getVar, displayView) into a new class.

Code: Select all

class View {
  private $_vars = array();
  private $_template;
  public function setVars(array $vars) {
    $this->_vars = $vars;
  }
  public function setVar($key, $value) {
    $this->_vars[$key] = $value;
  }
  public function getVar($key) {
    return $this->_vars[$key];
  }
  public function render($template) {
    $this->_template = $template;
    extract($this->_vars);
    include $this->_template;
  }
}

Code: Select all

abstract class ActionController {
 
    protected $name;
    //protected $viewData = array(); MOVED
 
    private static $pageDir;
 
    public static function setPageDir($pageDir){
        self::$pageDir = $pageDir;
    }
 
    public function setName($name) {
        $this->name = $name;
    }
 
    public function getName() {
        return $this->name;
    }
 
    //This function places a value in the $viewData array at the postion indicated by the key
    public function setVar($key, $value) {
        $this->_view->setVar($key, $value);
    }
 
    //This returns a value from the array located at the key value
    public function getVar($key) {
       return $this->_view->getVar($key);
    }
 
    //method_exists checks if the class method exists in the given object, then runs the displayView function if exists
    public function dispatchAction($action) {
        $actionMethod = "do" . ucfirst($action);
        if (!method_exists($this, $actionMethod)) {
            throw new FrontControllerException("Page not found!");
        }
 
        $this->$actionMethod();
        $this->displayView($action);
    }
 
    public function displayView($action) {
        if (!is_file(self::$pageDir . "/" . $this->getName() . "/" . $action . "View.php5")) {
            throw new FrontControllerException("Page not found!");
        }
 
        $this->_view->render(PAGE_DIR . "/" . $this->getName() . "/" . $action . "View.php5");
    }
 
    public function __set($key, $value)  {
        $this->setVar($key, $value);
    }
 
    public function __get($key) {
        return $this->getVar($key);
    }
   
}
 
?>
You'll want to add a constructor to create the View instance. I haven't added that because I'm not sure if you're overriding the constructor in your actual actions.

I still haven't directly answered your question, but this is a good first step. I guess we're moving away from the front controller and looking more at the actions and the view now.
kilbad
Forum Commoner
Posts: 28
Joined: Wed Apr 02, 2008 3:51 pm

Re: Front controller by Corbyn, where to put website wrapper?

Post by kilbad »

Ok, so I understand the syntax and actions of the functions in the View and Action classes. However, could you give me an example of how you would use/instantiate these classes?

Thanks in advance! I really appreciate all your help!

bt
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Re: Front controller by Corbyn, where to put website wrapper?

Post by Chris Corbyn »

I'm sorry but I don't really have to time to sit down and provide all the tried and tested code for this right now... I've got far too much work on as it is ;) Maybe someone else can chime in but I'm making assumptions that you are at least trying this yourself. The View class just needs to be instantiated in the constructor of ActionController:

Code: Select all

public function __construct() {
  $this->_view = new View();
}
Ideally you'd pass it in as an argument to the constructor so you can swap out the dependency later, but for now we can just do it this way.

Once you have the View separated out like this you can start to worry about how and where you'll create a composite view. I'm still happy creating composite views for each every page using include() or a view helper method but if you want an implicit layout then really all you need to do is include a layout.php instead of the actual page. Your layout.php would then include the relevant page template along with the standard header and footer etc.

Take a look at symfony's documentation even if you're not using it, it may give some inspiration:

http://www.symfony-project.org/book/1_0 ... View-Layer

Although this specific page relates to symfony, the first 50% of it gives a nice introduction to MVC:

http://www.symfony-project.org/book/1_0 ... ony-s-Code

symfony does have fairly right View:Controller coupling however, although I've never been bothered by it since it's not often (if even at all) that I need more than one View for a single action. I also haven't delved too deeply to see if that tight coupling isn't as tight as you'd think -- it may just be a convenient default to load a view matching the action name and may be possible to override at runtime.

You could branch a little further from the 1:1 View:ActionController relation and allow $view->render() be called from the action controller instead (so your front controller isn't doing so much decision making on which view to load).

Code: Select all

public function myAction() {
  $this->view->setVar('hello', 'world');
  $this->view->render('some-template.php');
}
The main reason my tutorial didn't have this level of separation was because I was focusing on the way the front controller works and not on a complete MVC tutorial.

EDIT | Basically what I'm trying to get across is that your question has nothing to do with a front controller and is to do with the view layer (which I didn't go into in that tutorial).
Post Reply